/**
 * 
 */
package br.edu.unifei.cct720.trabalho05.utils.formas;

import java.awt.Color;
import java.util.ArrayList;
import java.util.List;

import br.edu.unifei.cct720.trabalho05.utils.ponto.Ponto;
import br.edu.unifei.cct720.trabalho05.utils.ponto.Pontos;

/**
 * @author Moreno Mello
 *
 */
public class Circulo implements FormaGeometrica {

	private Ponto centro;
	private int raio;
	private Color cor;
	private double anguloInicial = -1;
	private double anguloFinal = -1;
	
	/**
	 * Construtor 
	 * 
	 * @param Ponto centro
	 * @param int raio
	 * @param Color cor
	 * 
	 */
	public Circulo(Ponto centro, int raio, Color cor) {
		this.centro = centro;
		this.raio = raio;
		this.cor = cor;
	}
	
	/**
	 * Construtor
	 * 
	 * @param Ponto centro
	 * @param int raio
	 * @param int anguloInicial
	 * @param int anguloFinal
	 * @param Color cor
	 * 
	 */
	public Circulo(Ponto centro, int raio, int anguloInicial, int anguloFinal, Color cor) {
		this.centro = centro;
		this.raio = raio;
		this.cor = cor;
		
		this.anguloInicial = Math.toRadians(anguloInicial);
		this.anguloFinal = Math.toRadians(anguloFinal);
		
		if (this.anguloInicial > Math.PI*2) 
			this.anguloInicial -= Math.PI*2;
		if (this.anguloFinal > Math.PI*2) 
			this.anguloFinal -= Math.PI*2;
	}
	
	/**
	 * Construtor 
	 * 
	 * @param Ponto centro
	 * @param int raio
	 * @param double anguloInicial
	 * @param double anguloFinal
	 * @param Color cor
	 * 
	 */
	public Circulo(Ponto centro, int raio, double anguloInicial, double anguloFinal, Color cor) {
		this.centro = centro;
		this.raio = raio;
		this.cor = cor;
		
		this.anguloInicial = anguloInicial;
		this.anguloFinal = anguloFinal;
		
		if (this.anguloInicial > Math.PI*2) 
			this.anguloInicial -= Math.PI*2;
		if (this.anguloFinal > Math.PI*2) 
			this.anguloFinal -= Math.PI*2;
	}

	/**
	 * Metodo responsavel por retornar todos 
	 * os pontos do circulo
	 * 
	 * @return Pontos
	 */
	public Pontos getPontos() {
		Pontos pontos = null;
		if (anguloInicial == -1)
			pontos = getPontosCirculo();
		else
			pontos = getPontosArco();
		
		return pontos;
	}
	
	/**
	 * Metodo responsavel por retornar todos
	 * os pontos do arco de circulo
	 * 
	 * @return Pontos
	 */
	private Pontos getPontosArco() {
		int xFinal = (int) Math.round(raio / Math.sqrt(2));
		int y = raio;
		
		List<Integer> setores = getSetoresArco();

		Pontos pontos = new Pontos();
		for (int x = 0; x <= xFinal; x++) {
			double dA = Math.pow(x + 1, 2) + Math.pow(y, 2) - Math.pow(raio, 2);
			double dB = Math.pow(x + 1, 2) + Math.pow((y - 1), 2) - Math.pow(raio, 2);
			double s = dA + dB;
			
			if (s > 0) 
				y--;			

			Ponto ponto = new Ponto(centro.getX() + x, centro.getY() + y, cor); 
			if (pertence(ponto, setores))
				pontos.add(ponto);
			
			ponto = new Ponto(centro.getX() - x, centro.getY() + y, cor);
			if (pertence(ponto, setores))
				pontos.add(ponto);

			ponto = new Ponto(centro.getX() + x, centro.getY() - y, cor);
			if (pertence(ponto, setores))
				pontos.add(ponto);
			
			ponto = new Ponto(centro.getX() - x, centro.getY() - y, cor);
			if (pertence(ponto, setores))
				pontos.add(ponto);
			
			ponto = new Ponto(centro.getX() + y, centro.getY() + x, cor);
			if (pertence(ponto, setores))
				pontos.add(ponto);
			
			ponto = new Ponto(centro.getX() - y, centro.getY() + x, cor);
			if (pertence(ponto, setores))
				pontos.add(ponto);
			
			ponto = new Ponto(centro.getX() + y, centro.getY() - x, cor);
			if (pertence(ponto, setores))
				pontos.add(ponto);
			
			ponto = new Ponto(centro.getX() - y, centro.getY() - x, cor);
			if (pertence(ponto, setores))
				pontos.add(ponto);

		}	
		return pontos;
	}
	
	/**
	 * Metodo responsavel por retornar todos os pontos de um circulo
	 *  
	 * @return Pontos
	 */
	public Pontos getPontosCirculo() {
		int xFinal = (int) Math.round(raio / Math.sqrt(2));
		int y = raio;

		Pontos pontos = new Pontos();
		for (int x = 0; x <= xFinal; x++) {
			double dA = Math.pow(x + 1, 2) + Math.pow(y, 2) - Math.pow(raio, 2);
			double dB = Math.pow(x + 1, 2) + Math.pow((y - 1), 2) - Math.pow(raio, 2);
			double s = dA + dB;
			
			if (s > 0) 
				y--;			

			pontos.add(new Ponto(centro.getX() + x, centro.getY() + y, cor));
			pontos.add(new Ponto(centro.getX() - x, centro.getY() + y, cor));
			pontos.add(new Ponto(centro.getX() + x, centro.getY() - y, cor));
			pontos.add(new Ponto(centro.getX() - x, centro.getY() - y, cor));
			pontos.add(new Ponto(centro.getX() + y, centro.getY() + x, cor));
			pontos.add(new Ponto(centro.getX() - y, centro.getY() + x, cor));
			pontos.add(new Ponto(centro.getX() + y, centro.getY() - x, cor));
			pontos.add(new Ponto(centro.getX() - y, centro.getY() - x, cor));

		}	
		
		return pontos;
	}
	
	/**
	 * Metodo responsavel por verificar se um
	 * determinado ponto pertence a algum dos
	 * setores especificados 
	 * 
	 * @param ponto
	 * @param setores
	 * @return
	 */
	protected boolean pertence(Ponto ponto, List<Integer> setores) {
		int setor = getSetor(ponto);
		if (setores.contains(setor) == false) return false;

		boolean retorno = true;
		if (setor == getSetor(anguloInicial)) {
			double angulo = Math.acos(Math.abs((double) (ponto.getX() - centro.getX())/raio));
			
			if (setor == 0 || setor == 1 || setor == 4 || setor == 5)
				angulo += Math.PI * (setor/4);
			else
				angulo = Math.PI * (setor/4 + 1) - angulo;			
			
			if ((anguloInicial < anguloFinal && angulo < anguloInicial) ||
				 (anguloFinal < anguloInicial && angulo > anguloFinal && angulo < anguloInicial) || 
				 (anguloInicial == anguloFinal))
				retorno = false;
		}
		if (setor == getSetor(anguloFinal)) {
			double angulo = Math.acos(Math.abs((double) (ponto.getX() - centro.getX())/raio));
			
			if (setor == 0 || setor == 1 || setor == 4 || setor == 5)
				angulo += Math.PI * (setor/4);
			else
				angulo = Math.PI * (setor/4 + 1) - angulo;

			if ((anguloInicial < anguloFinal && angulo >= anguloFinal) ||
				 (anguloFinal < anguloInicial && angulo > anguloFinal && angulo < anguloInicial) ||
				 (anguloInicial == anguloFinal))
				retorno = false;			
		}		

	   return retorno;
   }
	
	/**
	 * Metodo responsavel por identificar o
	 * setor no qual pertence o ponto
	 * espeficiado
	 * 
	 * @param Ponto p
	 * 
	 * @return int
	 */
	private int getSetor(Ponto p) {
		int setor = -1;
		int x = p.getX();
		int y = p.getY();
		int x0 = centro.getX();
		int y0 = centro.getY();
		
		double aux = Math.abs((double) (x - x0) / raio);
		double seno45 = Math.sqrt(2) / 2;

		
		if ((x >= x0) && (y <= y0) && aux > seno45) 
			setor = 0;
		else if ((x > x0) && (y <= y0) && aux <= seno45) 
			setor = 1;
		else if ((x <= x0) && (y <= y0) && aux < seno45) 
			setor = 2;
		else if ((x <= x0) && (y < y0) && aux >= seno45) 
			setor = 3;
		else if ((x <= x0) && (y >= y0) && aux > seno45) 
			setor = 4;
		else if ((x < x0) && (y >= y0) && aux <= seno45) 
			setor = 5;
		else if ((x >= x0) && (y >= y0) && aux < seno45) 
			setor = 6;
		else if ((x >= x0) && (y > y0) && aux >= seno45) 
			setor = 7;

		return setor;
	}

	/**
	 * Metodo que retorna todos os setores do circulo
	 * 
	 * @return List<Integer>
	 */
	private List<Integer> getSetoresArco() {
		List<Integer> setores = new ArrayList<Integer>();

		int setorInicial = getSetor(anguloInicial);
		int setorFinal = getSetor(anguloFinal);

		if (anguloInicial < anguloFinal) {
			for (int i = setorInicial; i <= setorFinal; i++)
				setores.add(i);
		} else if (anguloInicial > anguloFinal) {
			for (int i = 0; i <= setorFinal; i++)
				setores.add(i);
			for (int i = setorInicial; i < 8; i++)
				setores.add(i);
		} else {
			setores.add(setorInicial);
		}

		return setores;
	}

	/**
	 * Metodo que retorna o setor do referente
	 * angulo
	 * 
	 * @param double angulo
	 * 
	 * @return int
	 */
	protected int getSetor(double angulo) {
   	   return (int) (angulo / (Math.PI / 4));
	}

	
	// Metodos getters e setters
	public Ponto getCentro() {
		return centro;
	}

	public int getRaio() {
		return raio;
	}
	
	public double getAnguloInicial() {
		return anguloInicial;
	}

	public double getAnguloFinal() {
		return anguloFinal;
	}
		
	public Color getCor() {
		return cor;
	}
	
}
